package com.base.components.common.service.validation;

import com.base.components.common.doc.Scope;
import com.base.components.common.doc.annotation.Param;
import com.base.components.common.doc.annotation.RequestBodyModel;
import com.base.components.common.doc.annotation.RequestModel;
import com.base.components.common.exception.business.ArgumentException;
import com.base.components.common.util.ConvertUtil;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.web.bind.annotation.PathVariable;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Map;
import java.util.Set;

/**
 * CheckRequestParamAspect 请求参数效验处理
 *
 * 目前只实现了效验 Map 和 PathVariable 参数
 *
 * @author <a href="drakelee1221@gmail.com">LiGeng</a>
 * @version v1.0.0
 * @date 2019-01-02 15:35
 */
@Aspect
public class CheckRequestParamAspect {
  private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();

  @Pointcut("@annotation(com.base.components.common.doc.annotation.RequestBodyModel) "
       + "|| @annotation(com.base.components.common.doc.annotation.RequestModel)")
  public void requestModel() {}

  @Before("requestModel()")
  public void doBefore(JoinPoint joinPoint) {
    Signature signature = joinPoint.getSignature();
    if(signature instanceof MethodSignature){
      Method method = ((MethodSignature) signature).getMethod();
      if(method != null){
        RequestBodyModel requestBodyModel = method.getAnnotation(RequestBodyModel.class);
        RequestModel requestModel = method.getAnnotation(RequestModel.class);
        Object[] args = joinPoint.getArgs();
        if(args != null && (requestModel != null || requestBodyModel != null)){
          Annotation[][] parameterAnnotations = method.getParameterAnnotations();
          Map<String, Object> pathVariables = Collections.emptyMap();
          Set<Map> queryMaps = Sets.newHashSet();
          for (int i = 0; i < args.length; i++) {
            Object arg = args[i];
            PathVariable pathVariable = findPathVariable(parameterAnnotations[i]);
            if(pathVariable != null){
              if(pathVariables.equals(Collections.emptyMap())){
                pathVariables = Maps.newHashMap();
              }
              pathVariables.put(pathVariable.value(), arg);
            }
            else if (arg instanceof Map){
              queryMaps.add((Map)arg);
            }
          }
          checkParam(requestModel, requestBodyModel, pathVariables, queryMaps);
        }
      }
    }
  }

  private void checkParam(RequestModel requestModel, RequestBodyModel requestBodyModel,
                          Map<String, Object> pathVariables, Set<Map> queryMap){
    if(requestModel != null){
      for (Param param : requestModel.value()) {
        if(StringUtils.isNotBlank(param.checkEL())){
          if(param.requestScope().equals(Scope.PATH)){
            checkValue(param, pathVariables.get(param.name()));
          }
          else{
            for (Map map : queryMap) {
              checkQueryParam(map, param);
            }
          }
        }
      }
    }
    if(requestBodyModel != null){
      for (Param param : requestBodyModel.value()) {
        if(StringUtils.isNotBlank(param.checkEL())){
          for (Map map : queryMap) {
            checkQueryParam(map, param);
          }
        }
      }
    }
  }

  private void checkQueryParam(Map paramMap, Param param) {
    Object val = paramMap.get(param.name());
    checkValue(param, val);
  }

  private void checkValue(Param param, Object srcValue){
    if(param != null){
      if (param.required() && srcValue == null) {
        throw new ArgumentException(getErrorMsg(param));
      }
      //字段有值
      if (srcValue != null) {
        try {
          Object obj = ConvertUtil.convert(srcValue, param.dataType());
          Boolean check = EXPRESSION_PARSER.parseExpression(param.checkEL()).getValue(obj, Boolean.class);
          if (check == null || !check) {
            throw new ArgumentException(getErrorMsg(param));
          }
        } catch (Exception e) {
          throw new ArgumentException(getErrorMsg(param), e);
        }
      }
    }
  }

  private String getErrorMsg(Param param){
    String msg = param.value();
    int i = msg.indexOf(param.separator());
    if(i > 0){
      msg = msg.substring(0, i);
    }
    return String.format(param.errorMsg(), msg);
  }

  private PathVariable findPathVariable(Annotation[] annotations){
    for (Annotation annotation : annotations) {
      if(annotation instanceof PathVariable){
        return (PathVariable) annotation;
      }
    }
    return null;
  }
}
